#include "tbcore/geo/coord.hpp"

#include <srm/srm_types.h>

#include "tbcore/geo/conv.hpp"

TB_NAMESPACE_BEGIN

struct Coord::CoordImpl {
	CoordImpl() 
		: gcccoord_(nullptr), gcscoord_(nullptr), llzcoord_(nullptr), 
			gccdirty_(true), gcsdirty_(true), llzdirty_(true) {}

	CoordImpl(double lon, double lat, double height, int32 orm) 
		: gcccoord_(nullptr), gcscoord_(nullptr), llzcoord_(nullptr), 
			gccdirty_(true), gcsdirty_(true), llzdirty_(true) {
		InitWithLLZCoord(lon, lat, height, orm);
	}

	~CoordImpl() {
		TB_SAFE_DELETE(gcscoord_);
		TB_SAFE_DELETE(gcscoord_);
		TB_SAFE_DELETE(llzcoord_);
	}

	void InitWithLLZCoord(double lon, double lat, double height,  int32 orm) {
		if (llzdirty_) {
			llzdirty_ = false;

			if (!llzcoord_) {
				llzcoord_ = new LLZCoord();
			}

			llzcoord_->lon = lon;
			llzcoord_->lat = lat;
			llzcoord_->z = height;
			llzcoord_->orm = orm;
		}
	}

	void InitLLZCoord() {
		if (llzdirty_) {
			llzdirty_ = false;

			if (!llzcoord_) {
				llzcoord_ = new LLZCoord();
			}
		
			if (!gccdirty_ && gcccoord_) {
				geo::GCCToLLZ(*gcccoord_, *llzcoord_);
			} else if (!gcsdirty_ && gcscoord_) {
				geo::GCSToLLZ(*gcscoord_, *llzcoord_);
			}
		}
	}

	void InitGCCCoord() {
		if (gccdirty_) {
			gccdirty_ = false;

			if (!gcccoord_) {
				gcccoord_ = new GCCCoord();
			}

			if (!llzdirty_ && llzcoord_) {
				geo::LLZToGCC(*llzcoord_, *gcccoord_);
			} else if (!gccdirty_ && gcscoord_) {
				geo::GCSToGCC(*gcscoord_, *gcccoord_);
			}
		}
	}

	void InitGCSCoord() {
		if (gcsdirty_) {
			gcsdirty_ = false;

			if (!gcscoord_) {
				gcscoord_ = new GCSCoord();
			}

			if (!llzdirty_ && llzcoord_) {
				geo::LLZToGCS(*llzcoord_, *gcscoord_);
			} else if (!gccdirty_ && gcccoord_) {
				geo::GCCToGCS(*gcccoord_, *gcscoord_);
			}
		}
	}

	void SetLongitude(double lon) {
		if (!llzcoord_) {
			llzcoord_ = new LLZCoord();
		}
		llzcoord_->lon = lon;
		llzdirty_ = false;
	}

	void SetLatitude(double lat) {
		if (!llzcoord_) {
			llzcoord_ = new LLZCoord();
		}
		llzcoord_->lat = lat;
		llzdirty_ = false;
	}

	void SetHeight(double height) {
		if (!llzcoord_) {
			llzcoord_ = new LLZCoord();
		}
		llzcoord_->z = height;
		llzdirty_ = false;
	}

	mutable GCCCoord* gcccoord_;
	mutable GCSCoord* gcscoord_;
	mutable LLZCoord* llzcoord_;
	bool gccdirty_ : 1;
	bool gcsdirty_ : 1;
	bool llzdirty_ : 1;
};

Coord::Coord() : impl_(new CoordImpl()) {}

Coord::Coord(double lon, double lat, double height, int32 orm)
	: impl_(new CoordImpl(lon, lat, height, orm)) {

}

Coord::Coord(const GCCCoord& coord) 
	: impl_(new CoordImpl()) {
	impl_->gcccoord_ = new GCCCoord();
	*impl_->gcccoord_ = coord;
	impl_->gccdirty_ = false;
}

Coord::Coord(const GCSCoord& coord)
	: impl_(new CoordImpl()) {
	impl_->gcscoord_ = new GCSCoord();
	*impl_->gcscoord_ = coord;
	impl_->gcsdirty_ = false;
}

Coord::Coord(const LLZCoord& coord)
	: impl_(new CoordImpl()) {
	impl_->llzcoord_ = new LLZCoord();
	*impl_->llzcoord_ = coord;
	impl_->llzdirty_ = false;
}

Coord::Coord(const Coord& rhs) 
	: impl_(new CoordImpl()) {
	*impl_ = *rhs.impl_;
	if (rhs.impl_->llzcoord_) {
		impl_->llzcoord_ = new LLZCoord(*rhs.impl_->llzcoord_);
		impl_->llzdirty_ = false;
	}
	if (rhs.impl_->gcccoord_) {
		impl_->gcccoord_ = new GCCCoord(*rhs.impl_->gcccoord_);
		impl_->gcccoord_ = false;
	}
	if (rhs.impl_->gcscoord_) {
		impl_->gcscoord_ = new GCSCoord(*rhs.impl_->gcscoord_);
		impl_->gcsdirty_ = false;
	}
}

Coord& Coord::operator=(const Coord& rhs) {
	if (rhs.impl_->llzcoord_) {
		if (!impl_->llzcoord_) {
			impl_->llzcoord_ = new LLZCoord();
		}
		*impl_->llzcoord_ = *rhs.impl_->llzcoord_;
		impl_->llzdirty_ = false;
	}
	if (rhs.impl_->gcccoord_) {
		if (!impl_->gcccoord_) {
			impl_->gcccoord_ = new GCCCoord();
		}
		*impl_->gcccoord_ = *rhs.impl_->gcccoord_;
		impl_->gccdirty_ = false;
	}
	if (rhs.impl_->gcscoord_) {
		if (!impl_->gcscoord_) {
			impl_->gcscoord_ = new GCSCoord();
		}
		*impl_->gcscoord_ = *rhs.impl_->gcscoord_;
		impl_->gcsdirty_ = false;
	}
	return *this;
}

Coord::~Coord() { TB_SAFE_DELETE(impl_); }

double Coord::GetLon() const {
	return impl_->llzcoord_ ? impl_->llzcoord_->lon : 0.0;
}

void Coord::SetLon(double lon) {
	impl_->SetLongitude(lon);
}

double Coord::GetLat() const {
	return impl_->llzcoord_ ? impl_->llzcoord_->lat : 0.0;
}

void Coord::SetLat(double lat) {
	impl_->SetLatitude(lat);
}

double Coord::GetAlt() const {
	return impl_->llzcoord_ ? impl_->llzcoord_->z : 0.0;
}

void Coord::SetAlt(double height) {
	impl_->SetHeight(height);
}

const GCCCoord& Coord::AsGCC() const {
	impl_->InitGCCCoord();
	return *impl_->gcccoord_;
}

const LLZCoord& Coord::AsLLZ() const {
	impl_->InitLLZCoord();
	return *impl_->llzcoord_;
}

const GCSCoord& Coord::AsGCS() const {
	impl_->InitGCSCoord();
	return *impl_->gcscoord_;
}

void Coord::SetLLZ(double lon, double lat, double z) {
  if (!impl_->llzcoord_) {
    impl_->llzcoord_ = new LLZCoord();
  }
  impl_->llzcoord_->lon = lon;
  impl_->llzcoord_->lat = lat;
  impl_->llzcoord_->z = z;
  impl_->gccdirty_ = true;
  impl_->gcsdirty_ = true;
}

void Coord::SetGCC(double x, double y, double z) {
  if (!impl_->gcccoord_) {
    impl_->gcccoord_ = new GCCCoord();
  }
  impl_->gcccoord_->x = x;
  impl_->gcccoord_->y = y;
  impl_->gcccoord_->z = z;
  impl_->llzdirty_ = true;
  impl_->gcsdirty_ = true;
}

void Coord::SetGCS(double x, double y, double z, int32 cell) {
  if (!impl_->gcscoord_) {
    impl_->gcscoord_ = new GCSCoord();
  }
  impl_->gcscoord_->x = x;
  impl_->gcscoord_->y = y;
  impl_->gcscoord_->z = z;
  impl_->gcscoord_->cell = cell;
  impl_->llzdirty_ = true;
  impl_->gccdirty_ = true;
}

TB_NAMESPACE_END